﻿//////////////////////////////////////////////////////////////////////
// Copyright© 2021 
// Author: hchlqlz
// DateTime: 2021-08-17
// Describe: 调用栈相关
//////////////////////////////////////////////////////////////////////

#include "stacktrace.h"
#include <windows.h>
#include <DbgHelp.h>
#include <mutex>
#include <iostream>

namespace XLog
{
	void StackTrace_Initialize()
	{
		std::cout << "StackTrace_Initialize()" << std::endl;
		HANDLE process = GetCurrentProcess();
		SymInitialize(process, NULL, TRUE);
	}

	void StackTrace_Dispose()
	{
		std::cout << "StackTrace_Dispose()" << std::endl;
		HANDLE process = GetCurrentProcess();
		SymCleanup(process);
	}

	class StackTrace
	{
	public:
		StackTrace() { StackTrace_Initialize(); }
		~StackTrace() { StackTrace_Dispose(); }

		static void Initialize()
		{
			static StackTrace instance;
		}
	};

	void Initialize_StackTrace()
	{
		StackTrace::Initialize();
	}

	std::mutex mutex_stacktrace;
	std::string GetStackTrace(unsigned short usLevel)
	{
		if (usLevel == 0)
		{
			return "";
		}

		bool bDetailed = true;
		bool bShowInBlock = bDetailed;

		std::lock_guard<std::mutex> guard(mutex_stacktrace);

		string brackets = bDetailed ? "{}" : "[]";
		string newline = bShowInBlock ? "\n" : "";
		string tab = bShowInBlock ? "\t" : "";

		std::string res = std::format("{}DUMP{}{}{}", newline, brackets.at(0), newline, tab);

		std::string seq = bShowInBlock ? "\n\t" : ",";

		// [TODO] the number can be MAXUSHORT except specific system 
		const int MAX_LEVEL = 62;
		void* arrStack[ MAX_LEVEL ];

		usLevel = min(usLevel, MAX_LEVEL);

		HANDLE process = GetCurrentProcess();
		// [TODO] use hash to avoid multiple lookups 
		WORD frames = CaptureStackBackTrace(0, usLevel, arrStack, NULL);
	
		for (WORD i = 0; i < frames; ++i)
		{
			DWORD64 address = (DWORD64)arrStack[i];

			res.append(i == 0 ? "" : seq);

			if (bDetailed)
			{
				DWORD64 displacementSymbol = 0;
				char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
				memset(buffer, 0, sizeof(buffer));
				PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
				pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
				pSymbol->MaxNameLen = MAX_SYM_NAME;

				DWORD displacementLine = 0;
				IMAGEHLP_LINE64 line;
				line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

				if (!SymFromAddr(process, address, &displacementSymbol, pSymbol))
				{
					res.append(
						std::format("SymFromAddr({:X}) error[{}]", address, GetLastError()));
					continue;
				}

				const char* filename = "Unknown";
				int lineNo = 0;

				if (SymGetLineFromAddr64(process, address, &displacementLine, &line))
				{
					filename = line.FileName;
					lineNo = line.LineNumber;
				}

				res.append(
					std::format("{} at {}:{}"
						, pSymbol->Name, filename, lineNo));
			}
			else
			{
				res.append(
					std::format("{:X}", address));
			}
		}

		res.append(newline);
		res.push_back( brackets.at(1) );
		return res;
	}
}
